/**
 * Created : May 26, 2012
 *
 * @author pquiring
 */

import java.io.*;
import java.net.*;
import java.util.*;
import javax.net.ssl.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.filechooser.*;

import javaforce.*;
import javaforce.linux.*;

public class MainPanel extends javax.swing.JPanel {

  /**
   * Creates new form MainPanel
   */
  public MainPanel() {
    initComponents();
    This = this;
    tableModel = (DefaultTableModel)table.getModel();
    new File(JF.getUserPath() + "/Torrents").mkdirs();
//    JFLog.init(JF.getUserPath() + "/.jftorrent.log", true);
    loadConfig();
    updateList();
    startServer();
    startTorrents();
    initHttps();
    java.util.Timer timer = new java.util.Timer();
    timer.schedule(new TimerTask() {
      public void run() {
        java.awt.EventQueue.invokeLater(new Runnable() {
          public void run() {
           updateGUI();
          }
        });
      }
    }, 5000, 5000);
  }

  /**
   * This method is called from within the constructor to initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is always
   * regenerated by the Form Editor.
   */
  @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jToolBar1 = new javax.swing.JToolBar();
        addFile = new javax.swing.JButton();
        addURL = new javax.swing.JButton();
        jSeparator1 = new javax.swing.JToolBar.Separator();
        delete = new javax.swing.JButton();
        jSeparator2 = new javax.swing.JToolBar.Separator();
        start = new javax.swing.JButton();
        pause = new javax.swing.JButton();
        stop = new javax.swing.JButton();
        jSeparator3 = new javax.swing.JToolBar.Separator();
        editConfig = new javax.swing.JButton();
        jSeparator5 = new javax.swing.JToolBar.Separator();
        status = new javax.swing.JLabel();
        jSplitPane2 = new javax.swing.JSplitPane();
        jScrollPane1 = new javax.swing.JScrollPane();
        list = new javax.swing.JList();
        jScrollPane2 = new javax.swing.JScrollPane();
        table = new javax.swing.JTable();

        jToolBar1.setFloatable(false);
        jToolBar1.setRollover(true);

        addFile.setText("Add File");
        addFile.setFocusable(false);
        addFile.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        addFile.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        addFile.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addFileActionPerformed(evt);
            }
        });
        jToolBar1.add(addFile);

        addURL.setText("Add URL");
        addURL.setFocusable(false);
        addURL.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        addURL.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        addURL.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addURLActionPerformed(evt);
            }
        });
        jToolBar1.add(addURL);
        jToolBar1.add(jSeparator1);

        delete.setText("Delete");
        delete.setFocusable(false);
        delete.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        delete.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        delete.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                deleteActionPerformed(evt);
            }
        });
        jToolBar1.add(delete);
        jToolBar1.add(jSeparator2);

        start.setText("Start");
        start.setFocusable(false);
        start.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        start.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        start.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                startActionPerformed(evt);
            }
        });
        jToolBar1.add(start);

        pause.setText("Pause");
        pause.setFocusable(false);
        pause.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        pause.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        pause.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                pauseActionPerformed(evt);
            }
        });
        jToolBar1.add(pause);

        stop.setText("Stop");
        stop.setFocusable(false);
        stop.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        stop.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        stop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                stopActionPerformed(evt);
            }
        });
        jToolBar1.add(stop);
        jToolBar1.add(jSeparator3);

        editConfig.setText("Config");
        editConfig.setFocusable(false);
        editConfig.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        editConfig.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        editConfig.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                editConfigActionPerformed(evt);
            }
        });
        jToolBar1.add(editConfig);
        jToolBar1.add(jSeparator5);

        status.setText("Status:?");
        jToolBar1.add(status);

        jSplitPane2.setDividerLocation(150);
        jSplitPane2.setResizeWeight(0.1);

        list.setModel(listModel);
        list.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        list.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
                listValueChanged(evt);
            }
        });
        jScrollPane1.setViewportView(list);

        jSplitPane2.setLeftComponent(jScrollPane1);

        table.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {
                "Name", "#", "Size", "Done", "Status", "Seeds", "Peers", "Down Speed", "Up Speed", "ETA", "Uploaded", "Ratio", "Available"
            }
        ) {
            Class[] types = new Class [] {
                java.lang.String.class, java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class
            };
            boolean[] canEdit = new boolean [] {
                false, false, false, false, false, false, false, false, false, false, false, false, false
            };

            public Class getColumnClass(int columnIndex) {
                return types [columnIndex];
            }

            public boolean isCellEditable(int rowIndex, int columnIndex) {
                return canEdit [columnIndex];
            }
        });
        jScrollPane2.setViewportView(table);

        jSplitPane2.setRightComponent(jScrollPane2);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jToolBar1, javax.swing.GroupLayout.DEFAULT_SIZE, 609, Short.MAX_VALUE)
            .addComponent(jSplitPane2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jSplitPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 519, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents

  private void addFileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addFileActionPerformed
    addFile();
    updateList();
  }//GEN-LAST:event_addFileActionPerformed

  private void editConfigActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editConfigActionPerformed
    int orgPort = TorrentServer.port;
    ConfigDialog dialog = new ConfigDialog(null, true);
    dialog.setVisible(true);
    if (!dialog.accepted) return;
    if (config.port == orgPort) return;
    saveConfig();
    server.changePort(config.port);
    status.setText("?");
    for(int a=0;a<clients.size();a++) {
      clients.get(a).recontactTracker();
    }
  }//GEN-LAST:event_editConfigActionPerformed

  private void addURLActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addURLActionPerformed
    addURL();
    updateList();
  }//GEN-LAST:event_addURLActionPerformed

  private void listValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_listValueChanged
    updateTable();
  }//GEN-LAST:event_listValueChanged

  private void deleteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteActionPerformed
    int row = table.getSelectedRow();
    if (row == -1) return;
    int idx = (Integer)table.getValueAt(row, 1);
    TorrentClient client = clients.get(idx);
    client.close();
    clients.remove(client);
    removeTorrent(idx);
    updateTable();
    updateList();
  }//GEN-LAST:event_deleteActionPerformed

  private void startActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startActionPerformed
    int row = table.getSelectedRow();
    if (row == -1) return;
    int idx = (Integer)table.getValueAt(row, 1);
    TorrentClient client = clients.get(idx);
    client._start();
    config.torrent[idx].active = true;
    config.torrent[idx].paused = false;
    saveConfig();
  }//GEN-LAST:event_startActionPerformed

  private void pauseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseActionPerformed
    int row = table.getSelectedRow();
    if (row == -1) return;
    int idx = (Integer)table.getValueAt(row, 1);
    TorrentClient client = clients.get(idx);
    client.pause();
    config.torrent[idx].paused = true;
    saveConfig();
  }//GEN-LAST:event_pauseActionPerformed

  private void stopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_stopActionPerformed
    int row = table.getSelectedRow();
    if (row == -1) return;
    int idx = (Integer)table.getValueAt(row, 1);
    TorrentClient client = clients.get(idx);
    client._stop();
    config.torrent[idx].active = false;
    saveConfig();
  }//GEN-LAST:event_stopActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addFile;
    private javax.swing.JButton addURL;
    private javax.swing.JButton delete;
    private javax.swing.JButton editConfig;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JToolBar.Separator jSeparator1;
    private javax.swing.JToolBar.Separator jSeparator2;
    private javax.swing.JToolBar.Separator jSeparator3;
    private javax.swing.JToolBar.Separator jSeparator5;
    private javax.swing.JSplitPane jSplitPane2;
    private javax.swing.JToolBar jToolBar1;
    private javax.swing.JList list;
    private javax.swing.JButton pause;
    private javax.swing.JButton start;
    private javax.swing.JLabel status;
    private javax.swing.JButton stop;
    private javax.swing.JTable table;
    // End of variables declaration//GEN-END:variables

  private ArrayList<TorrentClient> clients = new ArrayList<TorrentClient>();
  public static Config config;
  private String configFolder = JF.getUserPath();
  private String configFile = "/.jftorrent.xml";
  private TorrentServer server;
  private DefaultListModel listModel = new DefaultListModel();
  private DefaultTableModel tableModel;
  public static MainPanel This;

  public static class Torrent {
    public String file;
    public String dest;
    public boolean active = true, paused = false;
  }

  public static class Config {
    public Torrent torrent[];
    public int port;
  }

  private void loadConfig() {
    defaultConfig();
    try {
      XML xml = new XML();
      FileInputStream fis = new FileInputStream(configFolder + configFile);
      xml.read(fis);
      xml.writeClass(config);
    } catch (FileNotFoundException e1) {
      defaultConfig();
    } catch (Exception e2) {
      JFLog.log(e2);
      defaultConfig();
    }
  }

  private void defaultConfig() {
    config = new Config();
    config.torrent = new Torrent[0];
    config.port = 6881;
  }

  private void saveConfig() {
    try {
      XML xml = new XML();
      FileOutputStream fos = new FileOutputStream(configFolder + configFile);
      xml.readClass("jftorrent", config);
      xml.write(fos);
      fos.close();
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  private void startServer() {
    server = new TorrentServer(config.port);
    server.start();
  }

  private void startTorrents() {
    for(int a=0;a<config.torrent.length;a++) {
      startTorrent(config.torrent[a].file, config.torrent[a].dest, config.torrent[a].active, config.torrent[a].paused);
    }
  }

  private boolean startTorrent(String file, String dest, boolean active, boolean paused) {
    int size = clients.size();
    for(int a=0;a<size;a++) {
      if (clients.get(a).torrent.equals(file)) return false;
    }
    TorrentClient client = new TorrentClient(file, dest, active, paused);
    client.start();
    clients.add(client);
    updateList();
    updateTable();
    return true;
  }

  public void updateList() {
    int idx = list.getSelectedIndex();
    listModel.removeAllElements();
    int downloading = 0;
    int seeding = 0;
//    int active = 0;
//    int inactive = 0;
    int size = clients.size();
    for(int a=0;a<size;a++) {
      TorrentClient client = clients.get(a);
      if (client.done) seeding++; else downloading++;
//      active++;
    }
    listModel.addElement("All (" + size + ")");
    listModel.addElement("Downloading (" + downloading + ")");
    listModel.addElement("Seeding (" + seeding + ")");
//    listModel.addElement("Active (" + active + ")");
//    listModel.addElement("Inactive (" + inactive + ")");
    list.setSelectedIndex(idx);
  }

  private String sizeToString(long size) {
    if (size < 1024 * 1024) return "" + size/1024 + "KBs";
    if (size < 1024 * 1024 * 1024) return "" + size/(1024 * 1024) + "MBs";
    return "" + size/(1024 * 1024 * 1024) + "GBs";
  }

  private String calcETA(TorrentClient client) {
    if (client.totalLength == client.downAmount) return "Done";
    int downSpeed = client.downSpeed;
    if (downSpeed == 0) return "inf";
    int seconds = (int)((client.totalLength - client.downAmount) / downSpeed);
    int minutes = seconds / 60;
    seconds = seconds - (minutes * 60);
    int hours = minutes / 60;
    minutes = minutes - (hours * 60);
    return String.format("%02d:%02d:%02d", hours,minutes,seconds);
  }

  private void addRow(TorrentClient client, int idx) {
    //name, #, size, done, status, seeds, peers, downSpeed, upSpeed, ETA, uploaded, ratio, available
    client.reset();
    tableModel.addRow(new Object[] {
      client.name, idx, sizeToString(client.totalLength), "" + client.downAmount * 100 / client.totalLength + "%",
      client.status, client.seeders, client.getNumPeers(),
      sizeToString(client.downSpeed), sizeToString(client.upSpeed), calcETA(client),
      sizeToString(client.upAmount), "" + client.upAmount / client.downAmount,
      "" + client.available
    });
    table.repaint();
  }

  private void updateRow(TorrentClient client, int row) {
    //name, #, size, done, status, seeds, peers, downSpeed, upSpeed, ETA, uploaded, ratio, available
    client.update();
    table.setValueAt("" + client.downAmount * 100 / client.totalLength + "%", row, 3);
    table.setValueAt(client.status, row, 4);
    table.setValueAt("" + client.seeders, row, 5);
    table.setValueAt(client.getNumPeers(), row, 6);
    table.setValueAt(sizeToString(client.downSpeed), row, 7);
    table.setValueAt(sizeToString(client.upSpeed), row, 8);
    table.setValueAt(calcETA(client), row, 9);
    table.setValueAt(sizeToString(client.upAmount), row, 10);
    table.setValueAt("" + client.upAmount / client.downAmount, row, 11);
    table.setValueAt("" + client.available, row, 12);
  }

  private void updateTable() {
    tableModel.setRowCount(0);
    int idx = list.getSelectedIndex();
    if (idx == -1) return;
    int size = clients.size();
    for(int a=0;a<size;a++) {
      TorrentClient client = clients.get(a);
      switch (idx) {
        case 0: addRow(client, a); break;
        case 1: if (!client.done) addRow(client, a); break;
        case 2: if (client.done) addRow(client, a); break;
      }
    }
  }

  private void addTorrent(String file, String dest) {
    Torrent newTorrent = new Torrent();
    newTorrent.file = file;
    newTorrent.dest = dest;
    newTorrent.active = true;
    newTorrent.paused = false;
    config.torrent = Arrays.copyOf(config.torrent, config.torrent.length + 1);
    config.torrent[config.torrent.length-1] = newTorrent;
    saveConfig();
  }

  private void removeTorrent(int idx) {
    int len = config.torrent.length;
    Torrent newList[] = new Torrent[len-1];
    System.arraycopy(config.torrent, 0, newList, 0, idx);
    System.arraycopy(config.torrent, idx+1, newList, idx, len - idx - 1);
    config.torrent = newList;
    saveConfig();
  }

  private void addFile() {
    JFileChooser chooser = new JFileChooser();
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    chooser.setMultiSelectionEnabled(false);
    chooser.setCurrentDirectory(new File(JF.getUserPath() + "/Torrents"));
    javax.swing.filechooser.FileFilter ffTorrent = new javax.swing.filechooser.FileFilter() {
      public boolean accept(File file) {
        if (file.isDirectory()) return true;
        if (file.getName().endsWith(".torrent")) return true;
        return false;
      }
      public String getDescription() {
        return "Torrent (*.torrent)";
      }
    };
    chooser.addChoosableFileFilter(ffTorrent);
    chooser.setFileFilter(ffTorrent);
//    chooser.setFileFilter(todo);
    if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return;
    File torrent = chooser.getSelectedFile();

    chooser = new JFileChooser();
    chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
    chooser.setMultiSelectionEnabled(false);
    chooser.setCurrentDirectory(new File(JF.getUserPath() + "/Downloads"));
    if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return;
    File dest = chooser.getSelectedFile();

    if (!startTorrent(torrent.getAbsolutePath(), dest.getAbsolutePath(), true, false)) return;
    addTorrent(torrent.getAbsolutePath(), dest.getAbsolutePath());
  }

  private String errmsg;
  private File urltorrent;

  private String getNameFromURL(String url) {
    int idx1 = url.lastIndexOf('/');
    int idx2 = url.lastIndexOf('.');
    if (idx2 == -1) idx2 = url.length();
    return url.substring(idx1+1, idx2);
  }

  private void addURL() {
    String urlStr = JFAWT.getString("Enter HTTP[S] URL:", "");
    if (urlStr == null) return;
    boolean http = false;
    boolean https = false;
    if (urlStr.startsWith("http://")) http = true;
    if (urlStr.startsWith("https://")) https = true;
    if (!http && !https) {
      JFAWT.showError("Error", "Unsupported URL");
      return;
    }
    try {
      JFTask task = new JFTask() {
        public boolean work() {
          try {
            String urlString = (String)this.getProperty("url");
            this.setLabel("Downloading : " + urlString);
            this.setProgress(5);
//            int idx = urlString.lastIndexOf(".");
//            String ext = urlString.substring(idx).toLowerCase();
            urltorrent = new File(JF.getUserPath() + "/Torrents/" + getNameFromURL(urlString) + ".torrent");
            URL url = new URL(urlString);
            HttpURLConnection uc = (HttpURLConnection)url.openConnection();
            this.setProgress(25);
            uc.connect();
            InputStream fis = uc.getInputStream();
            FileOutputStream fos = new FileOutputStream(urltorrent);
            JFLog.log("size=" + fis.available());
            if (uc.getResponseCode() != 200) throw new Exception("Error:" + uc.getResponseCode() + ":" + uc.getResponseMessage());
            long length = uc.getContentLength();
            if (length <= 0) throw new Exception("unknown length");
            if (!JF.copyAll(fis, fos, length)) throw new Exception("download failed");
            fis.close();
            fos.close();
            this.setProgress(50);
            this.setLabel("Complete");
            this.setProgress(100);
            errmsg = null;
            return true;
          } catch (Exception e) {
            errmsg = e.toString();
            return false;
          }
        }
      };
      task.setProperty("url", urlStr);
      ProgressDialog dialog = new ProgressDialog(null, true, task);
      dialog.setVisible(true);
      if (errmsg != null) {
        JFAWT.showError("Error", "Exception:" + errmsg);
      }
    } catch (Exception e) {
      JFAWT.showError("Error", "Exception:" + e);
    }

    JFileChooser chooser = new JFileChooser();
    chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
    chooser.setMultiSelectionEnabled(false);
    chooser.setCurrentDirectory(new File(JF.getUserPath() + "/Downloads"));
    if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return;
    File dest = chooser.getSelectedFile();

    if (!startTorrent(urltorrent.getAbsolutePath(), dest.getAbsolutePath(), true, false)) return;
    addTorrent(urltorrent.getAbsolutePath(), dest.getAbsolutePath());
  }

  /** This allows connections to untrusted hosts. */
  private void initHttps() {
    TrustManager[] trustAllCerts = new TrustManager[] {
      new X509TrustManager() {
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
          return null;
        }
        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
      }
    };
    // Let us create the factory where we can set some parameters for the connection
    try {
      SSLContext sc = SSLContext.getInstance("SSL");
      sc.init(null, trustAllCerts, new java.security.SecureRandom());
      SSLSocketFactory sslsocketfactory = (SSLSocketFactory) sc.getSocketFactory();  //this method will work with untrusted certs
    } catch (Exception e) {
      JFLog.log(e);
    }
  }

  public void close() {
    for(int a=0;a<clients.size();a++) {
      clients.get(a).close();
    }
  }

  private void updateGUI() {
    int size = table.getRowCount();
    for(int a=0;a<size;a++) {
      int idx = (Integer)table.getValueAt(a, 1);
      updateRow(clients.get(idx), a);
    }
    table.repaint();
  }

  public void setStatus(String txt) {
    status.setText("Status : " + txt);
  }
}
